﻿using System;
using System.Runtime.InteropServices;
using System.Threading;

namespace MediaRemoteServer
{
    class MediaControl : IDisposable
    {
        [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)]
        private struct mediacontrol_Exception
        {
            [MarshalAs(UnmanagedType.I4)]
            public int code;
            public string message;
        }

        [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)]
        private struct mediacontrol_StreamInformation
        {
            public mediacontrol_PlayerStatus streamstatus;
            public string url;
            public long position;
            public long length;
        }

        private enum mediacontrol_PlayerStatus
        {
            mediacontrol_UndefinedStatus,
            mediacontrol_InitStatus,
            mediacontrol_BufferingStatus,
            mediacontrol_PlayingStatus,
            mediacontrol_PauseStatus,
            mediacontrol_StopStatus,
            mediacontrol_EndStatus,
            mediacontrol_ErrorStatus,
        }

        private enum mediacontrol_PositionOrigin
        {
            mediacontrol_AbsolutePosition,
            mediacontrol_RelativePosition,
            mediacontrol_ModuloPosition,
        }

        private enum mediacontrol_PositionKey
        {
            mediacontrol_ByteCount,
            mediacontrol_SampleCount,
            mediacontrol_MediaTime,
        }

        [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)]
        private struct mediacontrol_Position
        {
            public mediacontrol_PositionOrigin origin;
            public mediacontrol_PositionKey key;
            public long value;
        }

        [DllImport("libvlc")]
        private static extern IntPtr mediacontrol_exception_create();

        [DllImport("libvlc")]
        private static extern void mediacontrol_exception_init(IntPtr exception);

        [DllImport("libvlc")]
        private static extern void mediacontrol_exception_cleanup(IntPtr exception);

        [DllImport("libvlc")]
        private static extern void mediacontrol_exception_free(IntPtr exception);

        [DllImport("libvlc")]
        private static extern IntPtr mediacontrol_new(int argc, string[] argv, IntPtr exception);

        [DllImport("libvlc")]
        private static extern IntPtr mediacontrol_new_from_instance(IntPtr p_libvlc, IntPtr exception);

        [DllImport("libvlc")]
        private static extern void mediacontrol_pause(IntPtr self, IntPtr exception);

        [DllImport("libvlc")]
        private static extern void mediacontrol_resume(IntPtr self, IntPtr exception);

        [DllImport("libvlc")]
        private static extern void mediacontrol_set_mrl(IntPtr self, string psz_file, IntPtr exception);

        [DllImport("libvlc")]
        private static extern void mediacontrol_start(IntPtr self, IntPtr a_position, IntPtr exception);

        [DllImport("libvlc")]
        private static extern string mediacontrol_get_mrl(IntPtr self, IntPtr exception);

        [DllImport("libvlc")]
        private static extern void mediacontrol_stop(IntPtr self, IntPtr exception);

        [DllImport("libvlc")]
        private static extern ushort mediacontrol_sound_get_volume(IntPtr self, IntPtr exception);

        [DllImport("libvlc")]
        private static extern void mediacontrol_sound_set_volume(IntPtr self, ushort volume, IntPtr exception);

        [DllImport("libvlc")]
        private static extern int mediacontrol_get_fullscreen(IntPtr self, IntPtr exception);

        [DllImport("libvlc")]
        private static extern int mediacontrol_set_visual(IntPtr self, int visual_id, IntPtr exception);

        [DllImport("libvlc")]
        private static extern void mediacontrol_set_fullscreen(IntPtr self, int b_fullscreen, IntPtr exception);

        [DllImport("libvlc")]
        private static extern void mediacontrol_exit(IntPtr self);

        [DllImport("libvlc")]
        private static extern IntPtr mediacontrol_get_media_position(IntPtr self, mediacontrol_PositionOrigin an_origin, mediacontrol_PositionKey a_key, IntPtr exception);

        [DllImport("libvlc")]
        private static extern void mediacontrol_set_media_position(IntPtr self, IntPtr a_position, IntPtr exception);

        [DllImport("libvlc")]
        private static extern IntPtr mediacontrol_get_stream_information(IntPtr self, mediacontrol_PositionKey a_key, IntPtr exception);

        [DllImport("libvlc")]
        private static extern int mediacontrol_get_rate(IntPtr self, IntPtr exception);

        [DllImport("libvlc")]
        private static extern void mediacontrol_set_rate(IntPtr self, int rate, IntPtr exception);

        private IntPtr instance;
        public Playlist playlist;

        public MediaControl(string vlcDir)
        {
            IntPtr pException = mediacontrol_exception_create();
            instance = mediacontrol_new(1, new string[] { "--plugin-path=" + vlcDir + "\\plugins" }, pException);
            mediacontrol_Exception exception = (mediacontrol_Exception)Marshal.PtrToStructure(pException, typeof(mediacontrol_Exception));
            mediacontrol_exception_free(pException);
            Console.WriteLine("Code: {0}\nMessage: {1}", exception.code, exception.message);
            playlist = null;
        }

        public MediaControl(LibVLC libVLCInstance)
        {
            IntPtr pException = mediacontrol_exception_create();
            instance = mediacontrol_new_from_instance(libVLCInstance.instance, pException);
            mediacontrol_Exception exception = (mediacontrol_Exception)Marshal.PtrToStructure(pException, typeof(mediacontrol_Exception));
            mediacontrol_exception_free(pException);
            Console.WriteLine("Code: {0}\nMessage: {1}", exception.code, exception.message);
        }

        public void play()
        {
            if (playlist != null && playlist.current() == null)
            {
                next(false);
            }

            setVideoWindow(0);

            mediacontrol_Position pos = new mediacontrol_Position();
            pos.key = mediacontrol_PositionKey.mediacontrol_MediaTime;
            pos.origin = mediacontrol_PositionOrigin.mediacontrol_AbsolutePosition;
            pos.value = 0;

            IntPtr pPosition = Marshal.AllocHGlobal(Marshal.SizeOf(typeof(mediacontrol_Position)));
            Marshal.StructureToPtr(pos, pPosition, true);

            IntPtr pException = mediacontrol_exception_create();
            mediacontrol_start(instance, pPosition, pException);
            Marshal.FreeHGlobal(pPosition);
            mediacontrol_Exception exception = (mediacontrol_Exception)Marshal.PtrToStructure(pException, typeof(mediacontrol_Exception));
            mediacontrol_exception_free(pException);
            Console.WriteLine("Code: {0}\nMessage: {1}", exception.code, exception.message);
        }

        public void setVideoWindow(int handle)
        {
            IntPtr pException = mediacontrol_exception_create();
            mediacontrol_set_visual(instance, handle, pException);
            mediacontrol_Exception exception = (mediacontrol_Exception)Marshal.PtrToStructure(pException, typeof(mediacontrol_Exception));
            mediacontrol_exception_free(pException);
            Console.WriteLine("Code: {0}\nMessage: {1}", exception.code, exception.message);
        }

        public void pause()
        {
            IntPtr pException = mediacontrol_exception_create();
            mediacontrol_pause(instance, pException);
            mediacontrol_Exception exception = (mediacontrol_Exception)Marshal.PtrToStructure(pException, typeof(mediacontrol_Exception));
            mediacontrol_exception_free(pException);
            Console.WriteLine("Code: {0}\nMessage: {1}", exception.code, exception.message);
        }

        public void stop()
        {
            IntPtr pException = mediacontrol_exception_create();
            mediacontrol_stop(instance, pException);
            mediacontrol_Exception exception = (mediacontrol_Exception)Marshal.PtrToStructure(pException, typeof(mediacontrol_Exception));
            mediacontrol_exception_free(pException);
            Console.WriteLine("Code: {0}\nMessage: {1}", exception.code, exception.message);
        }

        public void next()
        {
            next(true);
        }

        public void next(bool startPlaying)
        {
            string next = playlist.next();
            if (next != null)
            {
                setSource(next);
                if (startPlaying)
                {
                    play();
                }
            }
        }

        public void previous()
        {
            previous(true);
        }

        public void previous(bool startPlaying)
        {
            string previous = playlist.previous();
            if (previous != null)
            {
                setSource(previous);
                if (startPlaying)
                {
                    play();
                }
            }
        }

        public ushort getVolume()
        {
            ushort vol = 0;

            IntPtr pException = mediacontrol_exception_create();
            vol = mediacontrol_sound_get_volume(instance, pException);
            mediacontrol_Exception exception = (mediacontrol_Exception)Marshal.PtrToStructure(pException, typeof(mediacontrol_Exception));
            mediacontrol_exception_free(pException);
            Console.WriteLine("Code: {0}\nMessage: {1}", exception.code, exception.message);

            return vol;
        }

        public void setVolume(ushort volume)
        {
            IntPtr pException = mediacontrol_exception_create();
            mediacontrol_sound_set_volume(instance, volume, pException);
            mediacontrol_Exception exception = (mediacontrol_Exception)Marshal.PtrToStructure(pException, typeof(mediacontrol_Exception));
            mediacontrol_exception_free(pException);
            Console.WriteLine("Code: {0}\nMessage: {1}", exception.code, exception.message);
        }

        public int getRate()
        {
            int rate = 0;

            IntPtr pException = mediacontrol_exception_create();
            rate = mediacontrol_get_rate(instance, pException);
            mediacontrol_Exception exception = (mediacontrol_Exception)Marshal.PtrToStructure(pException, typeof(mediacontrol_Exception));
            mediacontrol_exception_free(pException);
            Console.WriteLine("Code: {0}\nMessage: {1}", exception.code, exception.message);

            return rate;
        }

        public void setRate(int rate)
        {
            IntPtr pException = mediacontrol_exception_create();
            mediacontrol_set_rate(instance, rate, pException);
            mediacontrol_Exception exception = (mediacontrol_Exception)Marshal.PtrToStructure(pException, typeof(mediacontrol_Exception));
            mediacontrol_exception_free(pException);
            Console.WriteLine("Code: {0}\nMessage: {1}", exception.code, exception.message);
        }

        public bool isFullScreen()
        {
            int result = 0;

            IntPtr pException = mediacontrol_exception_create();
            result = mediacontrol_get_fullscreen(instance, pException);
            mediacontrol_Exception exception = (mediacontrol_Exception)Marshal.PtrToStructure(pException, typeof(mediacontrol_Exception));
            mediacontrol_exception_free(pException);
            Console.WriteLine("Code: {0}\nMessage: {1}", exception.code, exception.message);

            return result != 0;
        }

        public void setFullScreen(bool fullScreen)
        {
            int bFullScreen = fullScreen ? 1 : 0;
            IntPtr pException = mediacontrol_exception_create();
            mediacontrol_set_fullscreen(instance, bFullScreen, pException);
            mediacontrol_Exception exception = (mediacontrol_Exception)Marshal.PtrToStructure(pException, typeof(mediacontrol_Exception));
            mediacontrol_exception_free(pException);
            Console.WriteLine("Code: {0}\nMessage: {1}", exception.code, exception.message);
        }

        public long getDuration()
        {
            long duration = 0;
            IntPtr pException = mediacontrol_exception_create();
            IntPtr pStreamInfo = mediacontrol_get_stream_information(instance, mediacontrol_PositionKey.mediacontrol_MediaTime, pException);
            mediacontrol_Exception exception = (mediacontrol_Exception)Marshal.PtrToStructure(pException, typeof(mediacontrol_Exception));
            mediacontrol_exception_free(pException);
            Console.WriteLine("Code: {0}\nMessage: {1}", exception.code, exception.message);
            mediacontrol_StreamInformation streamInfo = (mediacontrol_StreamInformation)Marshal.PtrToStructure(pStreamInfo, typeof(mediacontrol_StreamInformation));
            duration = streamInfo.length;
            return duration;
        }

        public long getPosition()
        {
            IntPtr pException = mediacontrol_exception_create();
            IntPtr pPosition = mediacontrol_get_media_position(instance, mediacontrol_PositionOrigin.mediacontrol_AbsolutePosition, mediacontrol_PositionKey.mediacontrol_MediaTime, pException);
            mediacontrol_Exception exception = (mediacontrol_Exception)Marshal.PtrToStructure(pException, typeof(mediacontrol_Exception));
            mediacontrol_exception_free(pException);
            Console.WriteLine("Code: {0}\nMessage: {1}", exception.code, exception.message);
            mediacontrol_Position position = (mediacontrol_Position)Marshal.PtrToStructure(pPosition, typeof(mediacontrol_Position));
            //Marshal.FreeHGlobal(pPosition);
            return position.value;
        }

        public void setPosition(long position)
        {
            mediacontrol_Position pos = new mediacontrol_Position();
            pos.key = mediacontrol_PositionKey.mediacontrol_MediaTime;
            pos.origin = mediacontrol_PositionOrigin.mediacontrol_AbsolutePosition;
            pos.value = position;

            IntPtr pPosition = Marshal.AllocHGlobal(Marshal.SizeOf(typeof(mediacontrol_Position)));
            Marshal.StructureToPtr(pos, pPosition, true);

            IntPtr pException = mediacontrol_exception_create();
            mediacontrol_set_media_position(instance, pPosition, pException);
            Marshal.FreeHGlobal(pPosition);
            mediacontrol_Exception exception = (mediacontrol_Exception)Marshal.PtrToStructure(pException, typeof(mediacontrol_Exception));
            mediacontrol_exception_free(pException);
            Console.WriteLine("Code: {0}\nMessage: {1}", exception.code, exception.message);
        }

        public void setSource(string source)
        {
            stop();
            IntPtr pException = mediacontrol_exception_create();
            mediacontrol_set_mrl(instance, source, pException);
            mediacontrol_Exception exception = (mediacontrol_Exception)Marshal.PtrToStructure(pException, typeof(mediacontrol_Exception));
            Console.WriteLine("Code: {0}\nMessage: {1}", exception.code, exception.message);
            mediacontrol_exception_cleanup(pException);
            mediacontrol_exception_init(pException);
            mediacontrol_set_visual(instance, -1, pException);
            Console.WriteLine("Code: {0}\nMessage: {1}", exception.code, exception.message);
            mediacontrol_exception_free(pException);
        }

        public string getSource()
        {
            String source = "";
            IntPtr pException = mediacontrol_exception_create();
            source = mediacontrol_get_mrl(instance, pException);
            mediacontrol_Exception exception = (mediacontrol_Exception)Marshal.PtrToStructure(pException, typeof(mediacontrol_Exception));
            mediacontrol_exception_free(pException);
            Console.WriteLine("Code: {0}\nMessage: {1}", exception.code, exception.message);
            return source;
        }

        public bool isRunning()
        {
            IntPtr pException = mediacontrol_exception_create();
            IntPtr pStreamInfo = mediacontrol_get_stream_information(instance, mediacontrol_PositionKey.mediacontrol_MediaTime, pException);
            mediacontrol_Exception exception = (mediacontrol_Exception)Marshal.PtrToStructure(pException, typeof(mediacontrol_Exception));
            mediacontrol_exception_free(pException);
            Console.WriteLine("Code: {0}\nMessage: {1}", exception.code, exception.message);
            mediacontrol_StreamInformation streamInfo = (mediacontrol_StreamInformation)Marshal.PtrToStructure(pStreamInfo, typeof(mediacontrol_StreamInformation));
            return streamInfo.streamstatus == mediacontrol_PlayerStatus.mediacontrol_PlayingStatus;
        }

        public bool isPaused()
        {
            IntPtr pException = mediacontrol_exception_create();
            IntPtr pStreamInfo = mediacontrol_get_stream_information(instance, mediacontrol_PositionKey.mediacontrol_MediaTime, pException);
            mediacontrol_Exception exception = (mediacontrol_Exception)Marshal.PtrToStructure(pException, typeof(mediacontrol_Exception));
            mediacontrol_exception_free(pException);
            Console.WriteLine("Code: {0}\nMessage: {1}", exception.code, exception.message);
            mediacontrol_StreamInformation streamInfo = (mediacontrol_StreamInformation)Marshal.PtrToStructure(pStreamInfo, typeof(mediacontrol_StreamInformation));
            return streamInfo.streamstatus == mediacontrol_PlayerStatus.mediacontrol_PauseStatus;
        }

        private void exit()
        {
            mediacontrol_exit(instance);
        }

        #region IDisposable Members

        public void Dispose()
        {
            exit();
        }

        #endregion
    }
}
